
//  Copyright (C) 2009-2011 Luca Piccioni
// 
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
// 
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
// 
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;

using Derm.Render;

namespace Derm.Scene.Mesh
{
	/// <summary>
	/// Mesh loader for OFF files. 
	/// </summary>	
	class MeshCodecPluginOFF : IMeshCodecPlugin
	{
		#region Constructors
		
		/// <summary>
		/// Constuct a MeshCodecPluginOFF.
		/// </summary>
		public MeshCodecPluginOFF()
		{
		
		}
		
		#endregion
		
		#region OFF File Format
		
		/// <summary>
		/// OFF file header.
		/// </summary>
		/// <remarks>
		/// This structure represents the information found at the beginning
		/// of the OFF file, ignoring the first 4 bytes.
		/// </remarks>
		private struct OffFileHeader
		{
			/// <summary>
			/// OffFileHeader constructor.
			/// </summary>
			/// <param name="sr">
			/// The StreamReader shall be positioned correctly on its base stream.
			/// A <see cref="StreamReader"/>
			/// </param>
			public OffFileHeader(StreamReader sr)
			{
				string line = sr.ReadLine();
				string[] tokens = line.Split(' ');
				
				if (tokens.Length != 3)
					throw new Exception("OFF file header line composed by "+tokens.Length+" tokens ("+line+").");
				
				// Parse OFF file header information
				mVertices = UInt32.Parse(tokens[0]);
				mFaces = UInt32.Parse(tokens[1]);
				mEdges = UInt32.Parse(tokens[2]);
			}
			
			/// <summary>
			/// Number of vertices.
			/// </summary>
			public uint mVertices;
			/// <summary>
			/// Number of faces.
			/// </summary>
			public uint mFaces;
			/// <summary>
			/// Number of edges.
			/// </summary>
			/// <remarks>
			/// This member could be safely ignored.
			/// </remarks>
			public uint mEdges;
		}
		
		/// <summary>
		/// OFF file vertex.
		/// </summary>
		private struct OffVertex
		{
			/// <summary>
			/// OffVertex constructor.
			/// </summary>
			/// <param name="sr">
			/// The StreamReader shall be positioned correctly on its base stream.
			/// A <see cref="StreamReader"/>
			/// </param>
			public OffVertex(StreamReader sr)
			{
				string line = sr.ReadLine();
				string[] tokens;
				float x, y, z;
				
				// Check end of file
				if (line == null)
					throw new Exception("Unexpected end of file found.");
				// Get tokens
				tokens = line.Split(' ');
				// Check token count
				if (tokens.Length != 3)
					throw new Exception("OFF vertex has "+tokens.Length+" tokens.");
				// Parse OFF vertex coordinates
				x = Single.Parse(tokens[0], NumberStyles.Float, CultureInfo.InvariantCulture);
				y = Single.Parse(tokens[1], NumberStyles.Float, CultureInfo.InvariantCulture);
				z = Single.Parse(tokens[2], NumberStyles.Float, CultureInfo.InvariantCulture);
				// Store vertex coordinates
				mVertex = new Vertex3f(x, y, z);
			}
			
			/// <summary>
			/// Vertex.
			/// </summary>
			public Vertex3f mVertex;
		}
		
		/// <summary>
		/// OFF file face.
		/// </summary>
		private struct OffFace
		{
			/// <summary>
			/// OffVertex constructor.
			/// </summary>
			/// <param name="sr">
			/// The StreamReader shall be positioned correctly on its base stream.
			/// A <see cref="StreamReader"/>
			/// </param>
			public OffFace(StreamReader sr)
			{
				string line = sr.ReadLine();
				string[] tokens;
				int offVertices;
				
				// Check end of file
				if (line == null)
					throw new Exception("Unexpected end of file found.");
				// Get tokens
				tokens = line.Split(' ');
				// Check token count
				if (tokens.Length < 2)
					throw new Exception("Not enought tokens for OFF face.");
				// Get number of vertices composing face
				offVertices = Int32.Parse(tokens[0]);
				if (offVertices != tokens.Length-1)
					throw new Exception("Tokens count in OFF face line not coherent with first token.");
				// Parse OFF face information
				mIndices = new int[offVertices];
				for (int i = 0; i < offVertices; i++)
					mIndices[i] = Int32.Parse(tokens[i+1], NumberStyles.Integer, CultureInfo.InvariantCulture);
			}
			
			/// <summary>
			/// Face vertices, specified with indices.
			/// </summary>
			public int[] mIndices;
		}
		
		#endregion
		
		#region IMeshCodecPlugin Implementation

		/// <summary>
		/// Determine whether this plugin is available for the current process.
		/// </summary>
		/// <returns>
		/// It returns a boolean value indicating whether the plugin is available for the current
		/// process.
		/// </returns>
		public bool CheckAvailability()
		{
			return (true);
		}

		/// <summary>
		/// Plugin name.
		/// </summary>
		/// <remarks>
		/// This name identity the plugin implementation. It shall be unique in the plugin set available at
		/// runtime.
		/// </remarks>
		public string Name { get { return ("MeshCodecPluginOFF"); } }
		
		/// <summary>
		/// Check whether a file stream is supported.
		/// </summary>
		/// <param name="path">
		/// A <see cref="System.String"/>
		/// </param>
		/// <returns>
		/// A <see cref="System.Boolean"/>
		/// </returns>
		/// <remarks>
		/// This routine determine whether a file is supported by examining the file
		/// name extention.
		/// </remarks>
		public bool IsSupported(string path)
		{
			// Lowering file path (case insesitive comparison)
			path = path.ToLower();
			
			if (path.EndsWith(".off") == true)
				return (true);
			
			return (false);
		}

		public Mesh Load(Stream fs)
		{
			if (fs == null)
				throw new ArgumentNullException("fs");
			
			using (StreamReader sr = new StreamReader(fs)) {
				Mesh mesh = new Mesh();
				ArrayBufferObject<Vertex3f> vertexPosition = new ArrayBufferObject<Vertex3f>(BufferObject.Hint.StaticCpuDraw);
				OffFileHeader offFileHeader;
				OffVertex[] offVertices;
				OffFace[] offFaces;
				uint[] mFaceBuffer, mFaceIndicesCount = new uint[4];
				string off;

				// Read OFF file signature
				if ((off = sr.ReadLine()) == null)
					throw new Exception("unexpected end of file found.");
				off = off.ToLower();
				Debug.Assert(off != null);
				if (off != "off")
					throw new Exception("not supported OFF file.");

				// Read OFF file header
				offFileHeader = new OffFileHeader(sr);

				// Read OFF vertices
				offVertices = new OffVertex[offFileHeader.mVertices];
				for (int i = 0; i < offFileHeader.mVertices; i++)
					offVertices[i] = new OffVertex(sr);
				// Read OFF faces
				mFaceBuffer = new uint[offFileHeader.mFaces];
				offFaces = new OffFace[offFileHeader.mFaces];
				for (int i = 0; i < offFileHeader.mFaces; i++)
					offFaces[i] = new OffFace(sr);

				// Here don't assume that OFF faces have the same number
				// of indices.

				// Get OFF faces statistics
				foreach (OffFace face in offFaces) {
					if ((face.mIndices.Length > 0) && (face.mIndices.Length < 5))
						mFaceIndicesCount[face.mIndices.Length - 1]++;
				}

				// Set the unique vertex buffer
				//vbo.AllocateOnClient(offFileHeader.mVertices);
				//for (uint v = 0; v < offFileHeader.mVertices; v++)
				//	vbo[v] = offVertices[v].mVertex;
				//mesh.PackedBuffers.SetVertexBuffer(vbo);

				// Append fundamental primitives (Points, Lines, Triangles, Quads)
				for (uint i = 0; i < 4; i++) {
					if (mFaceIndicesCount[i] > 0) {
						ElementBufferObject<uint> ibo = new ElementBufferObject<uint>(BufferObject.Hint.StaticCpuDraw);
						//Primitive vType = Primitive.Point;
						uint iboBufferIndex = 0;

						// Determine vertices type
						switch (i + 1) {
							case 1:
								Console.WriteLine("Appending Point primitive ({0} points).", mFaceIndicesCount[i]);
								//vType = Primitive.Point;
								break;
							case 2:
								Console.WriteLine("Appending Line primitive ({0} lines).", mFaceIndicesCount[i]);
								//vType = Primitive.Line;
								break;
							case 3:
								Console.WriteLine("Appending Triangle primitive ({0} triangles).", mFaceIndicesCount[i]);
								//vType = Primitive.Triangle;
								break;
								//case 4:
								//Console.WriteLine("Appending Quad primitive ({0} quads).", mFaceIndicesCount[i]);
								//vType = Mesh.Primitive.Quad;
								//break;
							default:
								//vType = Primitive.Point;
								break;
						}
						// Allocate memory
						//ibo.AllocateOnClient(mFaceIndicesCount[i]*(i+1));
						// Determine indices array
						foreach (OffFace face in offFaces) {
							//	if (face.mIndices.Length == i+1) for (int v = 0; v < i+1; v++)
							//		ibo[iboBufferIndex++] = (uint)face.mIndices[v];
						}
						// Set a new layout
						//mesh.SetSurface(vType, ibo);
					}
				}

				return (mesh);
			}
		}
		
		#endregion
	}
}
